package com.qunar.train.mock.database;

import com.qunar.train.mock.util.AegisLog;

import org.apache.ibatis.executor.BaseExecutor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;

import mockit.Mock;
import mockit.MockUp;

/**
 * Created by hongzhe.yao on 2018/9/26.
 *
 * contents： mybatis拦截器接口实现，实现数据库操作的统一拦截处理 将执行的真正sql内容打印出来 mock去掉sql的真实操作，不执行数据库的连接和操作
 */

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class})})
public class MybatisSqlInterceptorImpl implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Configuration configuration = mappedStatement.getConfiguration();
        Object parameter = null;
        if (invocation.getArgs().length > 1) {
            parameter = invocation.getArgs()[1];
        }

        String sqlId = mappedStatement.getId();
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        String sql = boundSql.getSql();

        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                String propertyName = parameterMapping.getProperty();
                if (boundSql.hasAdditionalParameter(propertyName)) {
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameter == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameter.getClass())) {
                    value = parameter;
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameter);
                    value = metaObject.getValue(propertyName);
                }
                if (value == null) {
                    value = "null";
                }
                sql = sql.replaceFirst("\\?", value.toString());
            }
        }

        sql = dealRedundantSpaceAndBlankLine(sql);
        sql = makeSqlStandard(sql);
        AegisLog.info("sql operation start");
        AegisLog.println("====== SQLID: " + sqlId);
        AegisLog.println("====== SQL: " + sql);
        AegisLog.info("sql operation end  ");

        mockQueryAndUpdate();
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // TODO Auto-generated method stub
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // TODO Auto-generated method stub
    }

    /**
     * 处理query和update函数，不执行数据库的真是操作
     */
    private void mockQueryAndUpdate() {
        new MockUp<BaseExecutor>() {
            @Mock
            public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {
                List<E> res = new ArrayList<>();
                return res;
            }

            @Mock
            public int update(MappedStatement ms, Object parameter) throws SQLException {
                return 0;
            }
        };
    }

    /**
     * 删除字符串多余的空格和空行 将字符串处理为一行
     */
    public static String dealRedundantSpaceAndBlankLine(String content) {
        if (content == null || content.length() == 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < content.length(); i++) {
            if (content.charAt(i) != '\r')
                sb.append(content.charAt(i));
        }
        String strAfterRemoveCR = sb.toString();
        if (strAfterRemoveCR == null || strAfterRemoveCR.length() == 0) {
            return "";
        }
        StringBuilder resultSB = new StringBuilder();
        String[] lines = strAfterRemoveCR.split("\n");
        for (String line : lines) {
            if (line == null) {
                continue;
            }
            if (line.indexOf("-- ") != -1) {
                continue;
            }
            if (!"".equals(line.trim())) {
                resultSB.append(dealSpaceOneLine(line));
            }
        }
        return resultSB.toString();
    }

    /**
     * 删除一行字符串中多余的空格，空格保留连续不超过1个
     */
    public static String dealSpaceOneLine(String line) {
        if (line == null || "".equals(line)) {
            return "";
        }
        int spaceCount = 0;
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < line.length(); i++) {
            char curChar = line.charAt(i);
            if (curChar == ' ') {
                spaceCount++;
                if (spaceCount <= 1) {
                    sb.append(' ');
                }
            } else if (curChar == '\t') {
                sb.append(' ');
            } else {
                spaceCount = 0;
                sb.append(curChar);
            }
        }
        return sb.toString();
    }

    /**
     * 字符串添加引号，满足sql语法，sql语句可直接运行
     */
    public static String makeSqlStandard(String sql) {
        if (sql == null || sql.length() == 0) {
            return "";
        }
        //select、update语句处理
        if (sql.indexOf("update ") != -1 || sql.indexOf("select ") != -1 || sql.indexOf("UPDATE ") != -1 || sql.indexOf("SELECT ") != -1) {
            return dealUpdateAndSelectSql(sql);
        } else if (sql.indexOf("INSERT ") != -1 || sql.indexOf("insert ") != -1) {
            return dealInsertSql(sql);
        } else {
            return sql;
        }

    }

    /**
     * select、update语句处理 暂时只支持“=”号赋值
     */
    public static String dealUpdateAndSelectSql(String sql) {
        StringBuilder resultSql = new StringBuilder();
        int flag1 = 0;
        char[] sqlArray = sql.toCharArray();
        String subsql = "";
        for (int i = 0; i < sqlArray.length; i++) {
            if (sqlArray[i] == '=') {
                flag1 = 1;
                resultSql.append(sqlArray[i]);
                continue;
            }

            if ((sqlArray[i] == ' ' || sqlArray[i] == ',' || sqlArray[i] == ';' || i == sqlArray.length - 1) && flag1 == 1) {
                if (subsql.length() < 1) {
                    resultSql.append(sqlArray[i]);
                    continue;
                }
                if (i == sqlArray.length - 1 && sqlArray[i] != ' ' && sqlArray[i] != ',' && sqlArray[i] != ';') {
                    subsql += sqlArray[i];
                }
                if (subsql.indexOf("'") != -1 || subsql.indexOf("\"") != -1) {
                    resultSql.append(subsql);
                } else {
                    if (isInteger(subsql) && subsql.length() < 5) {
                        resultSql.append(subsql);
                    } else if (subsql.indexOf('.') != -1) {
                        //连表查询不加引号
                        resultSql.append(subsql);
                    } else if (subsql.indexOf("UNIX_TIMESTAMP") != -1) {
                        //函数不能加引号
                        resultSql.append(subsql);
                    } else {
                        resultSql.append("'" + subsql + "'");
                    }
                }
                if (sqlArray[i] == ' ' || sqlArray[i] == ',' || sqlArray[i] == ';') {
                    resultSql.append(sqlArray[i]);
                }
                subsql = "";
                flag1 = 0;
                continue;
            }

            if (flag1 == 1) {
                subsql += sqlArray[i];
            } else {
                resultSql.append(sqlArray[i]);
            }
        }
        return resultSql.toString();
    }

    /**
     * insert语句处理
     */
    public static String dealInsertSql(String sql) {
        String[] sqlArray = new String[100];
        if (sql.indexOf("VALUES") != -1) {
            sqlArray = sql.split("VALUES");
        } else if (sql.indexOf("values") != -1) {
            sqlArray = sql.split("values");
        } else {
            return sql;
        }
        if (sqlArray.length < 2) {
            return sql;
        }

        StringBuilder resultSql = new StringBuilder();
        resultSql.append(sqlArray[0]);
        resultSql.append("VALUES");
        resultSql.append("(");

        String subSql = sqlArray[1].trim();
        if (subSql.indexOf("(") != -1) {
            subSql = subSql.split("\\(")[1];
            subSql = subSql.split("\\)")[0];
        }
        String[] subSqlArray = subSql.split(",");

        for (int i = 0; i < subSqlArray.length; i++) {
            if (subSqlArray[i].indexOf("'") != -1 || subSqlArray[i].indexOf("\"") != -1) {
                resultSql.append(" " + subSqlArray[i] + ",");
            } else {
                if (isInteger(subSqlArray[i].trim()) && subSqlArray[i].trim().length() < 5) {
                    resultSql.append(" " + subSqlArray[i].trim() + ",");
                } else if (subSqlArray[i].trim().length() == 0) {
                    resultSql.append(" " + subSqlArray[i].trim() + ",");
                } else {
                    resultSql.append(" '" + subSqlArray[i].trim() + "',");
                }
            }
        }
        //去除最后多余的逗号
        resultSql.deleteCharAt(resultSql.length() - 1);
        resultSql.append(")");
        return resultSql.toString();
    }

    /**
     * 方法一：用JAVA自带的函数 判断是否为整数
     *
     * @param str 传入的字符串
     * @return 是整数返回true, 否则返回false
     */
    public static boolean isNumeric(String str) {
        for (int i = str.length(); --i >= 0; ) {
            if (!Character.isDigit(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    /**
     * 方法二：推荐，速度最快 判断是否为整数
     *
     * @param str 传入的字符串
     * @return 是整数返回true, 否则返回false
     */

    public static boolean isInteger(String str) {
        Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
        return pattern.matcher(str).matches();
    }
}
